/*
 * Copyright 2008 FBK (http://www.fbk.eu/)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.fbk.it.hlt.jlsi.data;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Iterator;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

import cern.colt.matrix.impl.AbstractMatrix2D;
import cern.colt.matrix.impl.DenseDoubleMatrix2D;

/**
 * Reads in Sparse Binary Matrix File.
 * 
 * @author Claudio Giuliano
 * @version %I%, %G%
 * @since 1.0
 */
public class SparseBinaryMatrixFileReader implements MatrixFileReader {
	/**
	 * Define a static logger variable so that it references the Logger instance
	 * named <code>SparseBinaryMatrixFileReader</code>.
	 */
	static Logger logger = Logger.getLogger(SparseBinaryMatrixFileReader.class
			.getName());

	//
	private RandomAccessFile raf;

	/**
	 * The number of rows.
	 */
	private int nr;

	/**
	 * The number of columns.
	 */
	private int nc;

	/**
	 * The total number of non zero values.
	 */
	private int nz;

	//
	private FileChannel channel;

	/**
	 * Constructs a sparse binary matrix writer.
	 * 
	 * @param file
	 *            the file where to write the matrix.
	 */
	public SparseBinaryMatrixFileReader(File f) throws IOException {
		raf = new RandomAccessFile(f, "rw");
		channel = raf.getChannel();

		nr = 0;
		nc = 0;
		nz = 0;

		readHeader();

	} // end constructor

	/**
	 * Reads the number of rows, columns and non zero values. All values are
	 * 4-byte integers.
	 */
	private void readHeader() throws IOException {
		raf.seek(0);

		// read the number of rows
		nr = raf.readInt();

		// read the number of columns
		nc = raf.readInt();

		// read the number of non zero elements
		nz = raf.readInt();

		logger.debug("matrix: " + nr + " " + nc + " " + nz);
	} // end readHeader

	/**
	 * Reads the number of non zero values for a column and or each non-zero
	 * value in the column: row index and value. All values are 4-byte integers
	 * except value, which is a 4-byte float. All are in network byte order.
	 * 
	 * @param indexes
	 *            the column indexes.
	 * @param values
	 *            the column values.
	 */
	public AbstractMatrix2D read() throws IOException {
		DenseDoubleMatrix2D matrix = new DenseDoubleMatrix2D(nr, nc);

		for (int i = 0; i < nc; i++) {
			float[] col = readColumn();
			for (int j = 0; j < col.length; j++) {
				if (col[j] != 0)
					matrix.setQuick(j, i, col[j]);
			}
		} // end for i

		return matrix;
	} // end read

	//
	public float[] readColumn() throws IOException {
		float[] col = new float[nr];
		// read the number of elements stored in this column
		int l = raf.readInt();

		long position = raf.getFilePointer();
		long size = l * 4 * 2;

		MappedByteBuffer mbb = channel.map(FileChannel.MapMode.READ_ONLY,
				position, size);

		for (int i = 0; i < l; i++) {
			// int intero = mbb.getInt();
			// float virgola = mbb.getFloat();
			int intero = raf.readInt();
			float virgola = raf.readFloat();
			// col[mbb.getInt()] = mbb.getFloat();
			col[intero] = virgola;
		}
		/*
		 * // slow implementation int index; float value; for (int i=0;i<l;i++)
		 * { index = raf.readInt(); value = raf.readFloat(); col[index] = value;
		 * } // end for i
		 */
		// next column
		raf.seek(position + size);
		return col;
	} // end readColumn

	//
	public Entry[] readColumnArray() throws IOException {
		// read the number of elements stored in this column
		int l = raf.readInt();
		Entry[] array = new Entry[l];

		long position = raf.getFilePointer();
		long size = l * 4 * 2;

		MappedByteBuffer mbb = channel.map(FileChannel.MapMode.READ_ONLY,
				position, size);

		for (int i = 0; i < l; i++)
			array[i] = new Entry(mbb.getInt(), mbb.getFloat());

		// next column
		raf.seek(position + size);
		return array;
	} // end readColumnArray

	public Iterator columns() {
		return new SparseBinaryMatrixFileReaderIterator();
	} // end columns

	//
	public void close() throws IOException {
		raf.close();
	} // end close

	//
	public static void main(String[] args) throws Exception {
		String logConfig = System.getProperty("log-config");
		if (logConfig == null)
			logConfig = "log-config.txt";

		long begin = System.currentTimeMillis();

		PropertyConfigurator.configure(logConfig);

		if (args.length != 1) {
			System.out
					.println("Usage: java -mx1024M org.fbk.it.hlt.jlsi.data.SparseBinaryMatrixFileReader file");
			System.exit(1);
		}

		File file = new File(args[0]);

		SparseBinaryMatrixFileReader matrixFileReader = new SparseBinaryMatrixFileReader(
				file);
		// DenseDoubleMatrix2D matrix = (DenseDoubleMatrix2D)
		// matrixFileReader.read();

		// logger.debug("matrix: " + matrix.rows() + " X " + matrix.columns());

		int cc = 0;
		Iterator it = matrixFileReader.columns();
		while (it.hasNext()) {
			System.out.print("\ncol  #" + (cc++) + ": ");
			float[] col = (float[]) it.next();
			for (int i = 0; i < col.length; i++)
				System.out.print("(" + i + ", " + col[i] + ") ");
		} // end while

		long end = System.currentTimeMillis();
		System.out.println("matrix read in " + (end - begin) + " ms");
	} // end main

	//
	public class Entry {
		//
		private int index;

		//
		private float value;

		//
		public Entry(int index, float value) {
			this.index = index;
			this.value = value;
		} // end constructor

		//
		public int index() {
			return index;
		} // end index

		//
		public float value() {
			return value;
		} // end value

	} // end class Entry

	//
	class SparseBinaryMatrixFileReaderIterator implements Iterator {
		//
		// private SparseBinaryMatrixFileReader parent;

		//
		private int count;

		//
		SparseBinaryMatrixFileReaderIterator() {
			count = 0;
		} // end constructor

		//
		public boolean hasNext() {
			if (count < nc)
				return true;

			return false;
		} // end hasNext

		//
		public Object next() {
			Object obj = null;
			count++;
			try {
				obj = readColumn();
			} catch (IOException e) {
				logger.error(e);
			}

			return obj;
		} // end hasNext

		//
		public void remove() {
			// do nothing
		} // end remove
	} // end class SparseBinaryMatrixFileReaderIterator

} // end SparseBinaryMatrixFileReader